package com.xbrother.utils;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.springframework.util.StringUtils;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * lucene工具类
 *
 * @author huangchangjin
 * @date 2025/5/13 10:15
 */
@Slf4j
public class LuceneUtil {

    @Data
    static class Product {
        private String id;
        private String name;
        private String description;

        public Product() {
        }

        public Product(String id) {
            this.id = id;
        }

        public Product(String id, String name, String description) {
            this.id = id;
            this.name = name;
            this.description = description;
        }
    }

    public static List<Product> mockProducts() {
        List<Product> list = new ArrayList<>();
        list.add(new Product("1", "飞利浦led灯泡e27螺口暖白球泡灯家用照明超亮节能灯泡转色温灯泡", ""));
        list.add(new Product("1", "飞利浦led灯泡e27螺口暖白球泡灯家用照明超亮节能灯泡转色温灯泡", ""));
        list.add(new Product("2", "飞利浦led灯泡e14螺口蜡烛灯泡3W尖泡拉尾节能灯泡暖黄光源Lamp", ""));
        list.add(new Product("3", "雷士照明 LED灯泡 e27大螺口节能灯3W球泡灯 Lamp led节能灯泡", ""));
        list.add(new Product("4", "飞利浦 led灯泡 e27螺口家用3w暖白球泡灯节能灯5W灯泡LED单灯7w", ""));
        list.add(new Product("5", "飞利浦led小球泡e14螺口4.5w透明款led节能灯泡照明光源lamp单灯", ""));
        list.add(new Product("6", "飞利浦蒲公英护眼台灯工作学习阅读节能灯具30508带光源", ""));
        list.add(new Product("7", "欧普照明led灯泡蜡烛节能灯泡e14螺口球泡灯超亮照明单灯光源", ""));
        list.add(new Product("8", "欧普照明led灯泡节能灯泡超亮光源e14e27螺旋螺口小球泡暖黄家用", ""));
        list.add(new Product("9", "聚欧普照明led灯泡节能灯泡e27螺口球泡家用led照明单灯超亮光源", ""));
        return list;
    }

    /**
     * lucene搜索demo
     */
    public static void demo() throws Exception { // 创建内存索引目录
        // 1. 准备中文分词器
        IKAnalyzer analyzer = new IKAnalyzer();

        // 2. 索引
        List<Product> products = mockProducts();
        Directory directory = createIndex(analyzer, products);

        // 3. 查询器
        String keyword = "护眼带光源";
        //Query query = commonQuery(analyzer, keyword);
        Query query = multipleQuery(analyzer, keyword, new Product(null, "光", ""));

        // 4. 搜索
        IndexReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);
        int numberPerPage = 5;
        System.out.printf("当前一共有%d条数据%n", products.size());
        System.out.printf("查询关键字是：\"%s\"%n", keyword);

        // 4.1 普通查询
        //ScoreDoc[] hits = searcher.search(query, numberPerPage).scoreDocs;

        // 4.2 分页查询
        //ScoreDoc[] hits = pageSearch(query, searcher, 1, numberPerPage);

        // 4.3 优化分页查询
        ScoreDoc[] hits = pageSearchV2(query, searcher, 1, numberPerPage);

        // 5.1 显示查询结果
        showSearchResults(searcher, hits);

        // 5.2 显示查询结果[高亮]
        showSearchResults(searcher, hits, query, analyzer);

        // 6. 关闭查询
        reader.close();
    }

    public static Query commonQuery(IKAnalyzer analyzer, String keyword) throws ParseException {
        Query query = new QueryParser("name", analyzer).parse(keyword);
        return query;
    }

    public static Query multipleQuery(IKAnalyzer analyzer, String keyword, Product p) throws ParseException {
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        if (StringUtils.hasText(p.getId())) {
            builder.add(new TermQuery(new Term("id", p.getId())), BooleanClause.Occur.MUST);
        }

        if (StringUtils.hasText(p.getName())) {
            builder.add(new TermQuery(new Term("name", p.getName())), BooleanClause.Occur.SHOULD);
        }

        if (StringUtils.hasText(p.getDescription())) {
            builder.add(new TermQuery(new Term("description", p.getDescription())), BooleanClause.Occur.SHOULD);
        }

        // 查询的字段
        String[] fields = new String[]{"id", "name", "description"};

        // 关键字查询
        Query searchKeyQuery = new MultiFieldQueryParser(fields, analyzer).parse(keyword);

        builder.add(searchKeyQuery, BooleanClause.Occur.MUST);
        return builder.build();
    }

    /**
     * 显示搜索结果
     */
    public static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits)
            throws Exception {
        System.out.println("找到 " + hits.length + " 个命中.");
        System.out.println("序号\t匹配度得分\t结果");
        for (int i = 0; i < hits.length; ++i) {
            ScoreDoc scoreDoc = hits[i];
            // 获取当前结果的docid, 这个docid相当于就是这个数据在索引中的主键
            int docId = scoreDoc.doc;
            // 再根据主键docid，通过搜索器从索引里把对应的Document取出来
            Document d = searcher.doc(docId);
            List<IndexableField> fields = d.getFields();
            System.out.print((i + 1));
            System.out.print("\t" + scoreDoc.score);
            for (IndexableField f : fields) {
                String fieldName = f.name();
                String originContent = d.get(fieldName);
                System.out.print("\t" + fieldName + ":" + originContent);
            }
            System.out.println();
        }
    }

    /**
     * 高亮显示结果
     */
    public static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer)
            throws Exception {
        System.out.println("找到 " + hits.length + " 个命中.");
        System.out.println("序号\t匹配度得分\t结果");

        SimpleHTMLFormatter shf = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(shf, new QueryScorer(query));
        for (int i = 0; i < hits.length; ++i) {
            ScoreDoc scoreDoc = hits[i];
            int docId = scoreDoc.doc;
            Document d = searcher.doc(docId);
            List<IndexableField> fields = d.getFields();
            System.out.print((i + 1));
            System.out.print("\t" + scoreDoc.score);
            for (IndexableField f : fields) {
                String fieldName = f.name();
                String originContent = d.get(fieldName);
                TokenStream tokenStream = analyzer.tokenStream(fieldName, new StringReader(originContent));
                String highlighterContent = highlighter.getBestFragment(tokenStream, originContent);

                System.out.print("\t" + fieldName + ":" + (highlighterContent == null ? originContent : highlighterContent));
            }
            System.out.println("<br>");
        }
    }

    public static Directory createIndex(IKAnalyzer analyzer, List<Product> products) throws IOException {
        // 内存存储lucene索引
        Directory index = new ByteBuffersDirectory();
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        IndexWriter writer = new IndexWriter(index, config);

        for (Product p : products) {
            addDoc(writer, p);
        }
        writer.close();
        return index;
    }


    public static void addDoc(IndexWriter w, Product p) throws IOException {
        Document doc = new Document();
        doc.add(new StringField("id", p.id, Field.Store.YES));
        doc.add(new TextField("name", p.name, Field.Store.YES));
        doc.add(new TextField("description", p.description, Field.Store.YES));
        w.addDocument(doc);
    }

    public static void deleteDoc(IndexWriter w, Product p) throws IOException {
        w.deleteDocuments(new Term("id", p.id));
        w.commit();
    }

    public static void updateDoc(IndexWriter w, Product p) throws IOException {
        Document doc = new Document();
        doc.add(new TextField("id", p.id, Field.Store.YES));
        doc.add(new TextField("name", p.name, Field.Store.YES));
        doc.add(new TextField("description", p.description, Field.Store.YES));
        w.updateDocument(new Term("id", p.id), doc);
        w.commit();
    }

    /**
     * 分词demo
     */
    public static void ikDemo() throws IOException {
        IKAnalyzer analyzer = new IKAnalyzer();
        TokenStream ts = analyzer.tokenStream("name", "护眼带光源");
        ts.reset();
        while (ts.incrementToken()) {
            System.out.println(ts.reflectAsString(false));
        }
    }


    public static ScoreDoc[] pageSearch(Query query, IndexSearcher searcher, int pageNow, int pageSize)
            throws IOException {
        TopDocs topDocs = searcher.search(query, pageNow * pageSize);
        System.out.println("查询到的总条数\t" + topDocs.totalHits);
        ScoreDoc[] allScores = topDocs.scoreDocs;

        List<ScoreDoc> hitScores = new ArrayList<>();
        int start = (pageNow - 1) * pageSize;
        int end = pageSize * pageNow;
        for (int i = start; i < end; i++) {
            hitScores.add(allScores[i]);
        }

        ScoreDoc[] hits = hitScores.toArray(new ScoreDoc[]{});
        return hits;
    }

    public static ScoreDoc[] pageSearchV2(Query query, IndexSearcher searcher, int pageNow, int pageSize)
            throws IOException {

        int start = (pageNow - 1) * pageSize;
        if (0 == start) {
            TopDocs topDocs = searcher.search(query, pageNow * pageSize);
            return topDocs.scoreDocs;
        }
        // 查询数据， 结束页面自前的数据都会查询到，但是只取本页的数据
        TopDocs topDocs = searcher.search(query, start);
        // 获取到上一页最后一条
        ScoreDoc preScore = topDocs.scoreDocs[start - 1];
        // 查询最后一条后的数据的一页数据
        topDocs = searcher.searchAfter(preScore, query, pageSize);
        return topDocs.scoreDocs;
    }


}
